import queue
import threading
import cv2
import inspect
import ctypes


class CameraException(Exception):
    message = None

    # 初始化异常
    def __init__(self, message: str):
        # 初始化异常，定位异常信息描述
        self.message = message

    def __str__(self):
        return self.message


class BaseCamera:
    # 相机操作对象
    cam = None
    # 保存每一帧从rtsp流中读取到的画面，使用opencv读取，为BGR图片
    queue_image = queue.Queue(maxsize=1000)
    # 后台取帧线程
    thread = None
    # 相机Model
    camera_model = None
    flag = False

    # 相机基类
    def __init__(self, rtsp):
        """
        使用rtsp流初始化相机参数
        rtsp格式：rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
        username: 用户名。例如admin。
        password: 密码。例如12345。
        ip: 为设备IP。例如 192.0.0.64。
        port: 端口号默认为554，若为默认可不填写。
        codec：有h264、MPEG-4、mpeg4这几种。
        channel: 通道号，起始为1。例如通道1，则为ch1。
        subtype: 码流类型，主码流为main，辅码流为sub。
        """
        self.cam = cv2.VideoCapture(rtsp)
        if self.cam.isOpened():
            # 相机打开成功,启动线程读取数据
            self.thread = threading.Thread(target=self._thread, daemon=True)
            self.thread.start()
        else:
            # 打开失败，相机流错误
            raise CameraException("视频流接口访问失败")

    def _thread(self):
        """
        相机后台进程，持续读取相机
        opencv读取时会将信息存储到缓存区里，处理速度小于缓存区速度，会导致资源积累
        """
        # 线程一直读取视频流，将最新的视频流存在队列中
        while self.cam.isOpened():
            if self.flag:
                break
            ret, img = self.cam.read()
            if not ret or img is None:
                # 读取相机失败
                pass
            else:
                # 读取内容成功，将数据存放在缓存区
                if self.queue_image.full():
                    # 队列满，队头出队
                    self.queue_image.get()
                    # 队尾添加数据
                    self.queue_image.put(img)
                else:
                    # 队尾添加数据
                    self.queue_image.put(img)

    # 直接读取图片
    def read(self):
        """
        直接读取从rtsp流中获取到的图片，不进行额外加工
        可能为空，需做判空处理
        """
        return self.queue_image.get()

    # 读取视频帧
    def get_frame(self):
        """
        获取加工后的图片，可以直接返回给前端显示
        """
        img = self.queue_image.get()
        if img is None:
            return None
        else:
            # 压缩图片，否则图片过大，编码效率慢，视频延迟过高
            # img = cv2.resize(img, (0, 0), fx=0.25, fy=0.25)
            # 对图片进行编码
            # ret, jpeg = cv2.imencode('.jpeg', img)
            return img

    def _async_raise(tid, exctype):

        """raises the exception, performs cleanup if needed"""

        tid = ctypes.c_long(tid)

        if not inspect.isclass(exctype):
            exctype = type(exctype)

        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))

        if res == 0:

            raise ValueError("invalid thread id")

        elif res != 1:

            # """if it returns a number greater than one, you're in trouble,

            # and you should call it again with exc=NULL to revert the effect"""

            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)

            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_thread(self,thread):

        self._async_raise(thread.ident, SystemExit)


class CameraFactory:
    """
    相机工厂
    """
    # 存储实例化的所有相机
    cameras = {}

    @classmethod
    def get_camera(cls, camera_id: int,rtsp=0):
        # 通过相机id获取相机
        camera = cls.cameras.get(camera_id)
        if camera is None:
            # 查看是否存在相机，存在访问
            try:
                # camera_model = Camera.objects.get(id=camera_id)
                base_camera = BaseCamera(rtsp)
                if base_camera is not None:
                    cls.cameras.setdefault(camera_id, base_camera)
                    return cls.cameras.get(camera_id)
                else:
                    return None
            except Exception:
                # 相机不存在
                return None
            except CameraException:
                # 相机实例失败
                return None
        else:
            # 存在相机，直接返回
            return camera


